﻿//-----------------------------------------------------------------------
// <copyright>
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
namespace Fulcrum.ViewModelGenerator
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Linq;
    using System.Diagnostics;

    #endregion Usings

    public class ViewModelXmlConfiguration : IViewModelConfiguration
    {
        #region Properties

        public string Name { get; private set; }
        public string Namespace { get; private set; }
        public string Location { get; private set; }
        public bool ProvidePropertyChangedEvent { get; private set; }

        public string Summary { get; private set; }
        public string Remarks { get; private set; }

        SortedDictionary<string, string> usingsNamespaces = new SortedDictionary<string, string>(StringComparer.Ordinal);
        public ICollection<string> Usings { get { return this.usingsNamespaces.Values; } }

        private List<IViewModelPropertyConfiguration> properties = new List<IViewModelPropertyConfiguration>();
        public ICollection<IViewModelPropertyConfiguration> Properties { get { return this.properties; } }

        private List<IViewModelPassThroughPropertyConfiguration> passThroughProperties = new List<IViewModelPassThroughPropertyConfiguration>();
        public ICollection<IViewModelPassThroughPropertyConfiguration> PassThroughProperties { get { return this.passThroughProperties; } }

        private List<IViewModelAttachedPropertyConfiguration> attachedProperties = new List<IViewModelAttachedPropertyConfiguration>();
        public ICollection<IViewModelAttachedPropertyConfiguration> AttachedProperties { get { return this.attachedProperties; } }

        private List<IViewModelEventConfiguration> events = new List<IViewModelEventConfiguration>();
        public ICollection<IViewModelEventConfiguration> Events { get { return this.events; } }

        private string DefaultNamespace { get; set; }

        #endregion Properties

        #region Ctor

        public ViewModelXmlConfiguration(XElement xml)
            : this(xml, String.Empty)
        {
            // empty
        }

        public ViewModelXmlConfiguration(XElement xml, string defaultNamespace)
        {
            if (null == xml)
            {
                throw new ArgumentNullException("xml");
            }

            if ("ViewModel" != xml.Name)
            {
                throw new ArgumentException("Element 'ViewModel' expected", "xml");
            }

            if (false == xml.HasAttributes)
            {
                throw new ArgumentException("Element 'ViewModel' should not be empty", "xml");
            }

            if (null == defaultNamespace)
            {
                throw new ArgumentNullException("defaultNamespace");
            }

            this.DefaultNamespace = defaultNamespace;

            this.AddDefaults();
            this.Parse(xml);
            this.SortCollections();

            if (this.AttachedProperties.Count > 0)
            {
                AddUsingNamespace("System.Windows");
            }
        }

        private void AddUsingNamespace(string @namespace)
        {
            this.usingsNamespaces[@namespace] = @namespace;
        }

        #endregion Ctor

        #region AddDefaults

        private void AddDefaults()
        {
            this.AddDefaultUsings();
        }

        private void AddDefaultUsings()
        {
            var defaultUsings = new List<string>()
            {
                "System",
                "System.ComponentModel"
            };

            defaultUsings.ForEach(u => this.AddUsingNamespace(u));
        }

        #endregion AddDefaults

        #region Parse

        private void Parse(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name);

            this.ParseAttributes(xml);
            this.ParseUsings(xml);
            this.ParseProperties(xml);
            this.ParsePassThroughProperties(xml);
            this.ParseAttachedProperties(xml);
            this.ParseEvents(xml);
        }

        private void ParseAttributes(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name);

            this.Name = (string)xml.RequiredAttribute("Name");
            this.Namespace = (string)xml.OptionalAttribute("Namespace", this.DefaultNamespace);
            this.Location = (string)xml.OptionalAttribute("Location", String.Empty);
            this.ProvidePropertyChangedEvent = (bool)xml.OptionalAttribute("ProvidePropertyChangedEvent", true);

            this.Summary = (string)xml.OptionalAttribute("Summary", String.Empty);
            this.Remarks = (string)xml.OptionalAttribute("Remarks", String.Empty);
        }

        private void ParseUsings(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name && null != this.Usings);

            var usings = from u in xml.Elements("Using")
                         select u.Value;

            usings.ToList().ForEach(u => this.AddUsingNamespace(u));
        }

        private void ParseProperties(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name && null != this.Properties);

            var properties = from p in xml.Elements("Property")
                             select new ViewModelPropertyXmlConfiguration(p);

            properties.ToList().ForEach(p => 
                {
                    this.Properties.Add(p);
                    this.AddEventForProperty(p);
                });
        }

        private void ParsePassThroughProperties(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name && null != this.Properties);

            var properties = from p in xml.Elements("PassThroughProperty")
                             select new ViewModelPassThroughPropertyXmlConfiguration(p);

            properties.ToList().ForEach(p =>
            {
                this.PassThroughProperties.Add(p);
            });
        }

        private void ParseAttachedProperties(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name && null != this.AttachedProperties);

            var properties = from p in xml.Elements("AttachedProperty")
                             select new ViewModelAttachedPropertyXmlConfiguration(p);

            properties.ToList().ForEach(p =>
            {
                this.AttachedProperties.Add(p);
            });
        }

        private void AddEventForProperty(ViewModelPassThroughPropertyXmlConfiguration property)
        {
            var eventForPropertyChanged = new ViewModelEventXmlConfiguration(
                String.Format("{0}Changed", property.Name),
                String.Format("PropertyChangedEventArgs<{0}>", property.Type),
                String.Format("Raised when {0} has changed.", property.Name));

            this.Events.Add(eventForPropertyChanged);

        }

        private void AddEventForProperty(ViewModelPropertyXmlConfiguration property)
        {
            var eventForPropertyChanged = new ViewModelEventXmlConfiguration(
                String.Format("{0}Changed", property.Name),
                String.Format("PropertyChangedEventArgs<{0}>", property.Type),
                String.Format("Raised when {0} has changed.", property.Name));

            this.Events.Add(eventForPropertyChanged);

        }

        private void ParseEvents(XElement xml)
        {
            Debug.Assert(null != xml && "ViewModel" == xml.Name && null != this.Events);

            var events = from e in xml.Elements("Event")
                         select new ViewModelEventXmlConfiguration(e);

            events.ToList().ForEach(e => this.Events.Add(e));
        }

        #endregion Parse

        #region SortCollections

        private void SortCollections()
        {
            this.properties.Sort((a, b) => String.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
            this.events.Sort((a, b) => String.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
        }

        #endregion SortCollections
    }
}
